/*----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the 
License at http://www.mozilla.org/MPL/
	
Software distributed under the License is distributed on 
an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, 
either express or implied. 
See the License for the specific language governing rights and 
limitations under the License.
	
The Original Code is Fever RS ( ActionScript 3 ) Framework code.
	
The Initial Developer of the Original Code is Romain Ecarnot.
Portions created by Initial Developer are Copyright (C) 2006
the Initial Developer. All Rights Reserved.
	
Contributor(s):
	
Unless required by applicable law or agreed to in writing, 
software distributed under the License is distributed on 
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
OF ANY KIND, either express or implied.
See the License for the specific language governing permissions 
and limitations under the License.
----------------------------------------------------------------------------*/

package fever.data.overload 
{
	import flash.utils.getQualifiedClassName;
	
	import com.bourre.collection.Iterator;
	import com.bourre.collection.Set;
	
	import Array;
	import fever.error.NoSuchOverloadingException;	

	/**
	 * Overload a method.
	 * 
	 * <p>With overloading you have typically two or more methods with the 
	 * same name but with different signatures.<br />
	 * ActionScript 3.0 does not support overloading yet, that's why 
	 * <strong>Overload</strong> class provides system to simulate overloading 
	 * process.</p>
	 * 
	 * @includeExample OverloaderSample.as -noswf
	 * 
	 * @author Romain Ecarnot
	 */
	public class Overloader 
	{
		//--------------------------------------------------------------------
		// Private properties
		//--------------------------------------------------------------------
		
		private var _oScope : Object;
		private var _oSet : Set;
		
		
		//--------------------------------------------------------------------
		// Public API
		//--------------------------------------------------------------------
		
		/**
		 * Constructor.
		 * 
		 * @param scope Overloading scope where calling handler.
		 */
		public function Overloader( scope : Object = null )
		{
			_oScope = scope;
			_oSet = new Set( Overloading );
		}
		
		/**
		 * Adds overloading definition.
		 * 
		 * @example
		 * <listing version="3.0">
		 * 
		 * var o : Overload = new Overload( this );
	 	 * o.addOverloading( [ String, int ], _doByStringAndNumber );
		 * o.addOverloading( [ String, String ], _doByStringAndString );
		 * </listing>
		 * 
		 * @param typeSequence Sequence type
		 * @param handler Handler to trigger		 * @param checkNull If <code>true</code> then overloading definition 
		 * search failed if one of arguments is <code>null</code>. <br>
		 * Either each <code>null</code> argument is assimilate as 
		 * correct argument type according overloading definition.
		 */
		public function addOverloading( typeSequence : Array, 
			handler : Function, checkNull : Boolean = false ) : void
		{
			_oSet.add( new Overloading( typeSequence, handler, checkNull ) );
		}
		
		/**
		 * Adds overloading definition.
		 * 
		 * @example
		 * <listing version="3.0">
		 * 
		 * var o : Overload = new Overload( this );
	 	 * o.addOverloading( [ String, int ], _doByStringAndNumber );
	 	 * o.addOverloading( [ String, String ], _doByStringAndString );
	 	 * o.overload( arguments );
		 * </listing>
		 * 
		 * @param argSequence Arguments sequence to check
		 * 
		 * @return * Overloading method result
		 * 
		 * @throws fever.error.NoSuchOverloadingException if no overloading 
		 * definition is found
		 */
		public function overload( argSequence : Array ) : *
		{
			var l : int = argSequence.length;
			var it : Iterator = _oSet.iterator();
			
			while( it.hasNext() )
			{
				var o : Overloading = it.next( ) as Overloading;
				
				if( o.length == l )
				{
					if( o.matches( argSequence ) )
					{
						return o.handler.apply( _oScope, argSequence );
					}
				}
			}
			
			var a : Array = new Array();
			for( var i : int = 0; i < l; i++ )
			{
				a.push( getQualifiedClassName( argSequence [i] ) );	
			}
			throw new NoSuchOverloadingException( 
				"No overloading found for current arguments list [" + a + "]" 
			);
			return null;
		}
	}
}

import flash.utils.describeType;
import flash.utils.getQualifiedClassName;


/**
 * @private
 * 
 * Overloading definition structure.
 * 
 * @author Romain Ecarnot
 */
internal class Overloading
{
	//--------------------------------------------------------------------
	// Private properties
	//--------------------------------------------------------------------
		
	private var _aSequence : Array;
	private var _nLength : int;	private var _fHandler : Function;	private var _bCheckNull : Boolean;
	
	
	//--------------------------------------------------------------------
	// Public properties
	//--------------------------------------------------------------------
	
	/** Type sequence length. */
	public function get length( ) : int { return _nLength; }
	
	/** Handler to trigger. */	public function get handler( ) : Function { return _fHandler; }
	
	
	//--------------------------------------------------------------------
	// Public API
	//--------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * 
	 * @param typeSequence Data type sequence
	 * @param handler Handler to trigger when overloading definition 
	 * is found.
	 */	
	public function Overloading( typeSequence : Array, handler : Function, 
		checkNull : Boolean )
	{
		_aSequence = typeSequence.slice( 0 );
		_nLength = _aSequence.length;
		_fHandler = handler;
		_bCheckNull = checkNull;
	}
	
	/**
	 * Returns <code>true</code> if passed-in <code>argSequence</code> has 
	 * correct data type according type sequence defined in constructor.
	 * 
	 * @param argSequence Arguments collection
	 * 
	 * @see Overloader()
	 */
	public function matches( argSequence : Array ) : Boolean
	{
		var l : int = argSequence.length;
		
		for( var i : int = 0; i < l; i++ )
		{
			var o : * = argSequence[ i ];
			var t : Class = _aSequence[ i ];
			
			if( o == null )
			{
				return _bCheckNull ? false : true;
			}
			
			if( 
				!_isImplementationOf( o, t )
				&&
				!_isSubclassOf( o, t )
				&&
				( getQualifiedClassName( o ) != getQualifiedClassName( t ) )
			) return false;
		}
		
		return true;
	}
	
	private function _isImplementationOf( arg : *, type : Class ) : Boolean
	{
		return describeType( arg ).implementsInterface.(@type==getQualifiedClassName( type ) ).length() > 0;
	}
	
	private function _isSubclassOf( arg : *, type : Class ) : Boolean
	{
        return describeType( arg ).extendsClass.(@type==getQualifiedClassName( type ) ).length() > 0;
	}	
}
